﻿using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using System.Net.Http;
using System.Threading.Tasks;
using AutoMapper;
using CRM.Anyleads;
using CRM.Caches;
using CRM.Extensions;
using CRM.HttpProxy;
using CRM.Models;
using CRM.Models.GlobalSearch;
using CRM.ViewModels.GlobalSearch;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using Nager.PublicSuffix;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;

namespace CRM.Services
{
    public class GlobalSearchService : BaseService
    {
        private readonly AnyleadsClient _anyleadsClient;
        private readonly MyDbContext _dbContext;
        private readonly IMapper _mapper;
        private readonly ILogger<GlobalSearchService> _logger;
        private readonly IOptionsSnapshot<FakeRatioOptions> _fakeRatioOptions;
        private readonly MiHttpProxy _miHttpProxy;
        private DomainParser?  _domainParser;

        public GlobalSearchService(AnyleadsClient anyleadsClient, MiHttpProxy miHttpProxy, MyDbContext dbContext,
            IMapper mapper, ILogger<GlobalSearchService> logger, IOptionsSnapshot<FakeRatioOptions> fakeRatioOptions)
        {
            _anyleadsClient = anyleadsClient;
            _dbContext = dbContext;
            DbContext = dbContext;
            _mapper = mapper;
            _logger = logger;
            _fakeRatioOptions = fakeRatioOptions;
            _miHttpProxy = miHttpProxy;
        }

        public class FakeRatioOptions
        {
            public int FakeRatio { get; set; }
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="Keywords">关键词</param>
        /// <param name="Nation">国家id</param>
        /// <param name="HideInvalid">隐藏无效客户</param>
        /// <param name="HideAdded">隐藏已添加</param>
        /// <param name="FilterDuplicate">过滤重复</param>
        /// <param name="FilteredPlatforms">过滤平台</param>
        /// <param name="Contains">包含域名，false排除域名</param>
        public record SearchQuery(string Keywords, string Nation = "", bool HideInvalid = true, bool HideAdded = true,
            bool FilterDuplicate = true,
            [FromQuery(Name = "filteredPlatforms[]")]
            List<string> FilteredPlatforms = default,
            bool Contains = false);

        /// <summary>
        /// 搜索关键字
        /// </summary>
        /// <param name="query"></param>
        /// <returns>分页列表|null</returns>
        public async Task<DbSetExtension.PaginateStruct<GlobalSearchDto>> Search(SearchQuery query)
        {
            string nation, name = query.Keywords;
            if ((nation = await CheckNation(query.Nation)) == null)
            {
                return null;
            }

            ApiInvokedLog log;
            if ((log = await GetCachedLog(name, nation)) == null)
            {
                return null;
            }

            var root = JsonConvert.DeserializeObject<Root>(log.ResponseJson);
            var organic = root?.results.organic;

            if (null == organic) return null;
            var filteredOrganic = (IEnumerable<Organic>)organic;

            if (log.Id == 0)
            {
                var domainList = await DbContext.KeywordsDomains.Where(m => m.OemId == CurrentUser.OemId)
                    .Where(m => organic.Select(o => o.SiteUrl.GetFixedHashCode()).Contains(m.UrlHashCode))
                    .ToListAsync();
                var diffList = organic.Select(m => m.SiteUrl).Except(domainList.Select(m => m.Url)).ToList();
                foreach (var changeDomain in domainList)
                {
                    var newDomain = organic.First(m => m.SiteUrl == changeDomain.Url);
                    changeDomain.Url = newDomain.SiteUrl;
                    changeDomain.UrlShown = newDomain.urlShown;
                    changeDomain.Description = newDomain.desc;
                    changeDomain.Title = newDomain.title;
                    changeDomain.NationGeo = nation;
                    changeDomain.UserId = CurrentUser.Id;
                    changeDomain.GroupId = CurrentUser.GroupId;
                    changeDomain.CompanyId = CurrentUser.CompanyId;
                    changeDomain.OemId = CurrentUser.OemId;
                }
                if (diffList.Any())
                {
                    var diffDomainList = organic.
                        Where(m => diffList.Contains(m.SiteUrl))
                        .Select(o => new KeywordsDomain()
                        {
                            Url = o.SiteUrl,
                            UrlShown = o.urlShown,
                            Description = o.desc,
                            Title = o.title,
                            NationGeo = nation,
                            UserId = CurrentUser.Id,
                            GroupId = CurrentUser.GroupId,
                            CompanyId = CurrentUser.CompanyId,
                            OemId = CurrentUser.OemId,
                        }).ToList();
                    _dbContext.KeywordsDomains.AddRange(diffDomainList);
                    await _dbContext.SaveChangesAsync();
                }
            }

            if (query.FilterDuplicate) filteredOrganic = FilterDuplicate(filteredOrganic);
            if (query.FilteredPlatforms != null && query.FilteredPlatforms.Any())
                filteredOrganic = FilterPlatforms(filteredOrganic, query.FilteredPlatforms, query.Contains);

            #region 过滤已添加

            var relationIds = await DbContext.CustomerCompany.Where(m => m.OemId == CurrentUser.OemId)
                .Where(m => m.UserId == CurrentUser.Id)
                .Where(m => m.From == CustomerCompany.FromType.GlobalSearch)
                .Where(m => filteredOrganic.Select(r => r.SiteUrl).Contains(m.Url))
                .Select(m => new { m.RelationId, IsPrivate = m.IsExistPrivate == CustomerCompany.IsExistPrivateType.NO })
                .ToListAsync();
            var addedIds = relationIds.Where(m => m.IsPrivate)
                .Select(m => m.RelationId);
            var invalidIds = relationIds.Where(m => !m.IsPrivate)
                .Select(m => m.RelationId);

            #endregion

            var count = organic.Count;
            Request.TryGetValue("page", out var page);
            Request.TryGetValue("limit", out var limit);

            var intPage = Convert.ToInt32(page);
            intPage = intPage < 1 ? 1 : intPage;
            var intLimit = Convert.ToInt32(limit);
            intLimit = intLimit < 10 ? 10 : intLimit;
            DbSetExtension.PaginateStruct<KeywordsDomain> domainPageList;
            _logger.LogInformation("读取数据倍率： {Ratio}", _fakeRatioOptions.Value.FakeRatio);
            var ratio = count > 70 ? _fakeRatioOptions.Value.FakeRatio : 0;

            var sql = DbContext.KeywordsDomains
                // .Where(m => m.OemId == CurrentUser.OemId)
                .WhenWhere(query.HideAdded, m => !addedIds.Contains(m.Id))
                .WhenWhere(query.HideInvalid, m => !invalidIds.Contains(m.Id));

            var needFakeData = ratio > 0 && count > 10 && count > intLimit;
            if (needFakeData && intPage * intLimit > count)
            {
                filteredOrganic = filteredOrganic.Shuffle().Take(intLimit);
                domainPageList = await sql
                    .Where(m => filteredOrganic.Select(o => o.SiteUrl.GetFixedHashCode()).Contains(m.UrlHashCode))
                    .ToPaginateAsync(1, intLimit);
            }
            else
            {
                domainPageList = await sql.Where(m => filteredOrganic.Select(o => o.SiteUrl.GetFixedHashCode()).Contains(m.UrlHashCode))
                    .ToPaginateAsync(intPage, intLimit);
            }
            if (needFakeData)
            {
                domainPageList.CurrentPage = intPage;
                domainPageList.Total = count * (ratio + 1);
            }



            // TODO: 禁止调用
            // await GetDomainInfo(domainPageList.Items);
            await _dbContext.SaveChangesAsync();

            var domainDtoPageList = domainPageList.Transform(m =>
            {
                var dto = _mapper.Map<GlobalSearchDto>(m);
                dto.IsInvalid = invalidIds.Contains(dto.Id);
                dto.IsAdded = addedIds.Contains(dto.Id);
                return dto;
            });
            return domainDtoPageList;
        }

        /// <summary>
        /// 确认国家
        /// </summary>
        /// <param name="nation">国家id</param>
        /// <returns>国家编码</returns>
        private async Task<string> CheckNation(string nation)
        {
            if (string.IsNullOrEmpty(nation))
            {
                Error = "请选择国家";
                return null;
            }
            var geoName = await DbContext.Nations.Where(m => m.Id == int.Parse(nation))
                .Select(m => m.GeoName)
                .FirstOrDefaultAsync();
            if (string.IsNullOrEmpty(geoName))
            {
                Error = "请选择国家";
                return null;
            }

            return geoName;
        }

        /// <summary>
        /// 获取缓存记录
        /// </summary>
        /// <param name="name"></param>
        /// <param name="nation"></param>
        /// <returns></returns>
        private async Task<ApiInvokedLog> GetCachedLog(string name, string nation)
        {
            const string apiName = nameof(MiHttpProxy) + "." + nameof(MiHttpProxy.SerpCenter);
            var apiNameHashCode = apiName.GetFixedHashCode();
            var param = $"keywords={name}&nation={nation}";
            var paramsHashCode = param.GetFixedHashCode();

            var log = await _dbContext.ApiInvokedLogs.Where(m => m.ApiNameHashCode == apiNameHashCode)
                .Where(m => m.ParamsJsonHashCode == paramsHashCode)
                //.OrderByDescending(m => m.CreatedAt)
                .OrderByDescending(m => m.Id)
                .FirstOrDefaultAsync();

            // if (null == log || log.CreatedAt.AddDays(1) < DateTime.Now)
            if (null == log)
            {
                var res = await _miHttpProxy.SerpCenter(name, nation);
                if (res.IsSuccessStatusCode)
                {
                    var content = await res.Content.ReadAsStringAsync();
                    var json = JObject.Parse(content);
                    if (json.TryGetValue("error", out var error))
                    {
                        _logger.LogError("{Method}:name={Name}&nation={Nation},error={Error}", nameof(_miHttpProxy.SerpCenter), name, nation, error.ToString());
                        Error = "接口返回异常#251";
                        return null;
                    }
                    else
                    {
                        log = new ApiInvokedLog()
                        {
                            ApiName = apiName,
                            ParamsJson = param,
                            ResponseJson = content,
                            UserId = CurrentUser.Id,
                            GroupId = CurrentUser.GroupId,
                            CompanyId = CurrentUser.CompanyId,
                            OemId = CurrentUser.OemId,
                        };
                        DbContext.Add(log);
                    }

                }
                else
                {
                    _logger.LogError("{Method}:name={Name}&nation={Nation},errorCode={StatusCode},content={Content}", nameof(_miHttpProxy.SerpCenter), name, nation, (int)res.StatusCode, await res.Content.ReadAsStringAsync());
                    // 大概率是网络问题，尝试重试
                    Error = "接口请求超时#274";
                    return null;
                }
            }

            return log;
        }

        /// <summary>
        /// 过滤重复
        /// </summary>
        /// <param name="organics"></param>
        /// <returns></returns>
        private static IEnumerable<Organic> FilterDuplicate(IEnumerable<Organic> organics) => organics.DistinctBy(m => m.SiteUrl);

        /// <summary>
        /// 过滤平台
        /// 如何过滤平台
        /// 1. 判断相等
        /// 2. 判断是否以平台为子域名
        /// </summary>
        /// <param name="organics"></param>
        /// <param name="platforms"></param>
        /// <param name="queryContains"></param>
        /// <returns></returns>
        private static IEnumerable<Organic> FilterPlatforms(IEnumerable<Organic> organics,
            IEnumerable<string> platforms, bool queryContains)
        {
            static bool MatchPlatform(string domain, string filtered) =>
                (domain.Length == filtered.Length && domain == filtered) ||
                (domain.Length > filtered.Length && domain.EndsWith($".{filtered}"));

            return queryContains ?
                organics.Where(m => platforms.Any(f => MatchPlatform(m.Domain, f)))
                : organics.Where(m => !platforms.Any(f => MatchPlatform(m.Domain, f)));
        }

        public Task<IEnumerable<KeywordsDomain>> GetClueList(KeywordsDomain domains) => GetClueList(new[] { domains });

        /// <summary>
        /// 获取域名下的所有员工信息
        /// </summary>
        /// <param name="domains"></param>
        /// <returns></returns>
        public async Task<IEnumerable<KeywordsDomain>> GetClueList(IEnumerable<KeywordsDomain> domains)
        {
            var diffDomains = domains
                // .Where(m => m.EmployeeDetails is null || m.EmployeeDetails.UpdatedAt.AddDays(3) < DateTime.Now)
                .Where(m => m.EmployeeDetails is null // 为null代表还没有获取过
                            // 修复之前因为二级域名截取错误而获取到的错误信息, 即所有的员工公司域名都不是当前域名
                            || (m.Employees.Count > 0 && TryGetSecondLevelDomain(m.Domain, out var second) && !m.Employees.Any(e => e.CompanyDomain.EndsWith(second)))
                            // 更新三天前并且没有员工的数据
                            || (m.Employees.Count == 0 && m.EmployeeDetails.UpdatedAt.AddDays(3) < DateTime.Now))
                .ToArray();
            if (diffDomains.Any())
            {
                foreach (var diffDomain in diffDomains)
                {
                    if (!TryGetSecondLevelDomain(diffDomain.Domain, out var secondLevelDomain))
                    {
                        continue;
                    }
                    var res = await _anyleadsClient.EnrichCompany(secondLevelDomain);
                    if (res.Success)
                    {
                        diffDomain.EmployeeDetails = new EmployeeDetails()
                        {
                            EmployeeJson = res.Raw,
                            UpdatedAt = DateTime.Now,
                        };
                    }
                    else
                    {
                        _logger.LogError("{Method}:domain={Domain},code={Code},error={Error},Raw={Raw}"
                            , nameof(_anyleadsClient.EnrichCompany), secondLevelDomain, res.StatusCode, res.Error, res.Raw);
                        Error = res switch
                        {
                            // 代理服务器超时
                            { StatusCode: 404 } => "获取联系人数据超时,请重试#336",
                            _ => "获取联系人数据失败,请重试#337",
                        };
                        await _dbContext.SaveChangesAsync();
                        return domains;
                    }
                }

                await _dbContext.SaveChangesAsync();
            }

            return domains;
        }

        /// <summary>
        /// 详情数据
        /// </summary>
        /// <param name="id"></param>
        /// <returns></returns>
        public async Task<object> Info(int id)
        {
            var row = await _dbContext.KeywordsDomains
                .Where(m => m.Id == id)
                // .Where(m => m.OemId == CurrentUser.OemId)
                .FirstAsync();
            await GetDomainInfo(row);
            await GetClueList(row);
            return new
            {
                Info = _mapper.Map<GlobalSearchInfoDto>(row)
            };
        }

        /// <summary>
        /// 获取页面的社交信息
        /// </summary>
        /// <param name="domains"></param>
        public Task GetDomainInfo(KeywordsDomain domains) => GetDomainInfo(new[] { domains });
        public async Task GetDomainInfo(IEnumerable<KeywordsDomain> domains)
        {
            const string apiName = nameof(Anyleads) + "." + nameof(AnyleadsClient.ExtractEmailsFromUrls);
            var apiNameHashCode = apiName.GetFixedHashCode();
            var noDetailUrlDic = domains.Where(d => d.UrlDetails == null || d.UrlDetails.UpdatedAt.AddDays(1) < DateTime.Now)
                .Select(d => new { d.Url, Param = $"url={d.Url}", Domain = d })
                .ToDictionary(x => x.Url, x => new { x.Param, ParamsHashCode = x.Param.GetFixedHashCode(), x.Domain });
            if (!noDetailUrlDic.Any()) return;
            var yesterday = DateTime.Now.Subtract(TimeSpan.FromDays(1));
            var logs = DbContext.ApiInvokedLogs.Where(m => m.ApiNameHashCode == apiNameHashCode)
                .Where(m => noDetailUrlDic.Select(u => u.Value.ParamsHashCode).Contains(m.ParamsJsonHashCode))
                // .Where(m => m.CreatedAt > yesterday)
                .AsEnumerable()
                .DistinctBy(m => m.ParamsJsonHashCode)
                .ToDictionary(m => m.ParamsJsonHashCode, m => m.Response);
            foreach (var domain in noDetailUrlDic)
            {
                if (logs.TryGetValue(domain.Value.ParamsHashCode, out var response))
                {
                    domain.Value.Domain.UrlDetails = new UrlDetails()
                    { DetailJson = response.ToString(), UpdatedAt = DateTime.Now };
                }
                else
                {
                    var res = await _anyleadsClient.ExtractEmailsFromUrls(domain.Key);
                    if (res.Success)
                    {
                        var log = new ApiInvokedLog()
                        {
                            ApiName = apiName,
                            ParamsJson = domain.Value.Param,
                            ResponseJson = res.Raw,
                            UserId = CurrentUser.Id,
                            GroupId = CurrentUser.GroupId,
                            CompanyId = CurrentUser.CompanyId,
                            OemId = CurrentUser.OemId,
                        };
                        DbContext.Add(log);
                        domain.Value.Domain.UrlDetails = new UrlDetails()
                        { DetailJson = log.ResponseJson, UpdatedAt = DateTime.Now };
                    }
                    else
                    {
                        _logger.LogError("{Method}:url={Url},error={@Error}", nameof(_anyleadsClient.ExtractEmailsFromUrls), domain.Key, res.Error);
                        await DbContext.SaveChangesAsync();
                        return;
                    }
                }
            }

            await DbContext.SaveChangesAsync();
        }
        
        bool TryGetSecondLevelDomain(string domain, [NotNullWhen(true)]out string? subdomain)
        {
            _domainParser ??= new DomainParser(new WebTldRuleProvider());
            
            subdomain = null;
            if (!_domainParser.IsValidDomain(domain))
            {
                return false;
            }

            subdomain = _domainParser.Parse(domain).RegistrableDomain;
        
            return true;
        }
    }




    public class Image
    {
        public string alt { get; set; }
        public string href { get; set; }
        public string source { get; set; }
        public int pos_overall { get; set; }
    }

    public class Organic
    {
        public int pos { get; set; }
        public string url { get; set; }

        public string Domain => new Uri(SiteUrl).Host;

        public string desc { get; set; }
        public string title { get; set; }
        public string urlShown { get; set; }
        public int pos_overall { get; set; }

        /// <summary>
        /// 抓取网址的原始url。  
        /// bing搜索会使用自己的网址跳转，导致所有网址是bing.com
        /// </summary>
        public string SiteUrl =>
            url.StartsWith("https://www.bing.com") && !string.IsNullOrEmpty(urlShown) ? urlShown : url;
    }

    public class Factoid
    {
        public string title { get; set; }
        public string content { get; set; }
    }

    public class RelatedSearch
    {
        public string url { get; set; }
        public string title { get; set; }
        public int pos_overall { get; set; }
        public List<string> related_searches { get; set; }
    }

    public class Knowledge
    {
        public string title { get; set; }
        public List<Factoid> factoids { get; set; }
        public string subtitle { get; set; }
        public string description { get; set; }
        public List<RelatedSearch> related_searches { get; set; }
    }

    public class FeaturedSnippet
    {
        public string url { get; set; }
        public string desc { get; set; }
        public string title { get; set; }
        public string url_shown { get; set; }
        public int pos_overall { get; set; }
    }

    public class Search
    {
        public string url { get; set; }
        public string title { get; set; }
    }

    public class Source
    {
        public string url { get; set; }
        public string title { get; set; }
        public string url_shown { get; set; }
    }

    public class RelatedQuestion
    {
        public int pos { get; set; }
        public Search search { get; set; }
        public string question { get; set; }
        public int pos_overall { get; set; }
        public Source source { get; set; }
        public string answer { get; set; }
    }

    public class SearchInformation
    {
        public string query { get; set; }
        public string showing_results_for { get; set; }
        public int total_results_count { get; set; }
    }

    public class Result
    {
        // public List<object> paid { get; set; }
        // public List<Image> images { get; set; }
        public List<Organic> organic { get; set; }

        // public Knowledge knowledge { get; set; }
        // public List<FeaturedSnippet> featured_snippet { get; set; }
        // public List<RelatedSearch> related_searches { get; set; }
        // public List<RelatedQuestion> related_questions { get; set; }
        // public SearchInformation search_information { get; set; }
        // public int total_results_count { get; set; }
        // public string url { get; set; }
        // public string job_id { get; set; }
        // public int page { get; set; }
        // public int status_code { get; set; }
        // public string created_at { get; set; }
        // public string updated_at { get; set; }
    }

    public class Root
    {
        public Result results { get; set; }
    }
}